use super::{
    proto::{addr, make_ack, make_pong, types},
    resp::Message,
    serial::ModeSConn,
};
use crate::{
    conn::proto::read,
    error::{AppErr, AppErrMap},
    utils::get_mut,
};
use std::sync::Arc;

const NOTIFY_EVENT: &'static str = "notify-event";

impl ModeSConn {
    async fn read_once(&self) -> Result<Message, AppErr> {
        let buf = read(get_mut(&self)).await?;
        Ok(Message::new(buf))
    }

    async fn handle_notify(&self, msg: Message) -> Result<(), AppErr> {
        self.window.emit(NOTIFY_EVENT, msg).print_if_err();
        Ok(())
    }

    async fn handle_self(&self, msg: Message) -> Result<(), AppErr> {
        match msg.types() {
            types::ACK => {
                self.recv_mq.notify(msg).await;
            }

            types::PING => {
                self.write_raw(&make_pong(msg.src())).await?;
            }

            types::PONG => {
                self.recv_mq.notify(msg).await;
            }

            types::RES => {
                self.write_raw(&make_ack(msg.src(), msg.seq())).await?;
                self.recv_mq.notify(msg).await;
            }

            types::RESULT => {
                self.result_mq.notify(msg).await;
            }

            types::NOTIFY_ACK => {
                self.write_raw(&make_ack(msg.src(), msg.seq())).await?;
                self.handle_notify(msg).await?;
            }

            types::NOTIFY => {
                self.handle_notify(msg).await?;
            }

            _ => {}
        }

        Ok(())
    }

    async fn exec_once(&self) -> Result<(), AppErr> {
        let msg = self.read_once().await?;
        // msg.print();
        match msg.dst() {
            addr::IPC => self.handle_self(msg).await?,
            _ => {}
        }

        Ok(())
    }
}

pub async fn read_loop(conn: Arc<ModeSConn>) {
    loop {
        let ret = tokio::select! {
            _ = conn.exit_sem.acquire() => {
                break;
            }
            ret = conn.exec_once() => {
                ret
            }
        };
        if let Err(AppErr::Io(e)) = ret {
            println!("io: {}", e);
            break;
        }
        ret.print_if_err();
    }

    println!("read exit");
}
